package it.edgeworks.dsession.impl.memcache;

import com.danga.MemCached.MemCachedClient;
import com.danga.MemCached.SockIOPool;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import java.io.IOException;
import java.util.*;

/**
 * @author Ivan Diana
 * @version $Revision: 1 $
 */
public class SimpleCacheClient extends MemCachedClient {

    // create a static client as most installs only need
    private Properties properties;

    private String prefix;

    private boolean enabled;

    // Logger for this class and subclasses
    protected final Log logger = LogFactory.getLog(getClass());

    private static String SERVERS = "SERVERS";
    private static String WEIGHT = "WEIGHT";
    private static String INIT_CONN = "INIT_CONN";
    private static String MIN_CONN = "MIN_CONN";
    private static String MAX_CONN = "MAX_CONN";
    private static String MAINT_SLEEP = "MAINT_SLEEP";
    private static String MAX_IDLE = "MAX_IDLE";
    private static String SOCKET_TO = "SOCKET_TO";
    private static String SOCKET_CONNECT_TO = "SOCKET_CONNECT_TO";
    private static String NAGLE = "NAGLE";
    private static String COMPRESS_ENABLE = "COMPRESS_ENABLE";
    private static String COMPRESS_THRESHOLD = "COMPRESS_THRESHOLD";
    private static String ENABLED = "ENABLED";
    private static String PREFIX = "PREFIX";

    // set up connection pool once at class load
    public SimpleCacheClient(String resourceConfig) throws Exception {

        super();

        try {

            ClassLoader classLoader = getClass().getClassLoader();
            this.properties = new Properties();
            this.properties.load(classLoader.getResourceAsStream(resourceConfig));

        } catch (IOException e) {
            logger.error("Error reading memcache config file");
            throw e;
        } finally {
            logger.info("MemCache config read");
        }

        logger.info(this.properties.getProperty(PREFIX));
        prefix = this.properties.getProperty(PREFIX);

        enabled = Boolean.parseBoolean(this.properties.getProperty(ENABLED));
        logger.info(this.properties.get(SERVERS));
        logger.info(this.properties.get(ENABLED));
        try {

            // server list and weights
            String servers = this.properties.getProperty(SERVERS);
            ArrayList<String> serverList = new ArrayList<String>();
            if (servers.contains(",")) {
                StringTokenizer st = new StringTokenizer(servers, ",");
                while (st.hasMoreTokens()) {
                    String token = st.nextToken();
                    serverList.add(token);
                }
            } else {
                serverList.add(servers);
            }

            String weights = this.properties.getProperty(WEIGHT);
            ArrayList<Integer> weightList = new ArrayList<Integer>();
            if (weights.contains(",")) {
                StringTokenizer st = new StringTokenizer(weights, ",");
                while (st.hasMoreTokens()) {
                    String token = st.nextToken();
                    weightList.add(Integer.parseInt(token));
                }
            } else {
                weightList.add(Integer.parseInt(weights));
            }

            // grab an instance of our connection pool
            SockIOPool pool = SockIOPool.getInstance();

            // set the servers and the weights
            pool.setServers(serverList.toArray(new String[serverList.size()]));
            pool.setWeights(weightList.toArray(new Integer[weightList.size()]));

            // set some basic pool settings
            // 5 initial, 5 min, and 250 max conns
            // and set the max idle time for a conn
            // to 6 hours
            pool.setInitConn(Integer.parseInt(this.properties.getProperty(INIT_CONN)));
            pool.setMinConn(Integer.parseInt(this.properties.getProperty(MIN_CONN)));
            pool.setMaxConn(Integer.parseInt(this.properties.getProperty(MAX_CONN)));
            pool.setMaxIdle(Integer.parseInt(this.properties.getProperty(MAX_IDLE)));

            // set the sleep for the maint thread
            // it will wake up every x seconds and
            // maintain the pool size
            pool.setMaintSleep(Long.parseLong(this.properties.getProperty(MAINT_SLEEP)));

            // set some TCP settings
            // disable nagle
            // set the read timeoutInSeconds to 3 secs
            // and don't set a connect timeoutInSeconds
            pool.setNagle(Boolean.parseBoolean(this.properties.getProperty(NAGLE)));
            pool.setSocketTO(Integer.parseInt(this.properties.getProperty(SOCKET_TO)));
            pool.setSocketConnectTO(Integer.parseInt(this.properties.getProperty(SOCKET_CONNECT_TO)));

            // initialize the connection pool
            pool.initialize();
            logger.debug(pool.isInitialized() + " check initialization");


            // lets set some compression on for the client
            // compress anything larger than 64k
            this.setCompressEnable(Boolean.parseBoolean(this.properties.getProperty(COMPRESS_ENABLE)));
            //this.setCompressThreshold( Long.parseLong(this.properties.getProperty(COMPRESS_THRESHOLD)) );
            // set compression threshhold to 4 KB (default: 15 KB)  
            //this.setCompressThreshold(4096);
            // turn on storing primitive types as a string representation
            this.setPrimitiveAsString(false);

            logger.info("MemCache config init");

        } catch (Exception e) {
            logger.error("Error on MemCache config init: " + e.getMessage());
            throw e;
        }

    }

    /* (non-Javadoc)
     * @see com.danga.MemCached.MemCachedClient#get(java.lang.String)
     */
    public Object get(String key) {

        key = this.prefix + "-" + key;

        if (this.enabled) {
            key = processKey(key);

            try {
                logger.debug("getting cached object as " + key);
                Object ret = super.get(key);
                logger.debug("getting cached object: " + ret);
                return ret;
            } catch (Exception e) {
                logger.error("Error on reading object from cache");
                return null;
            }
        } else {
            return null;
        }
    }

    @Override
    public boolean set(String key, Object value, Date expiry) {
        key = this.prefix + "-" + key;

        logger.info(this.enabled);
        if (this.enabled) {
            key = processKey(key);

            try {
                boolean response = super.set(key, value, expiry);
                //Se l'oggetto è stato cachato
                if (response) {
                    logger.debug("Object stored in cache with key " + key + " EXPIRY " + expiry.toString());
                }

                return response;
            } catch (Exception e) {
                logger.error(
                        "[Exception cacheclient - " +
                                "Impossibile settare cache oggetto " + key + " ]");
                return false;
            }
        } else {
            return false;
        }
    }

    public boolean set(String key, Object object, int offsetSecond) throws Exception {

        key = this.prefix + "-" + key;

        logger.info(this.enabled);
        if (this.enabled) {
            key = processKey(key);

            try {
                //Setto la data di scadenza dell'oggetto da cachare
                GregorianCalendar calendar = (GregorianCalendar) GregorianCalendar.getInstance();
                calendar.setTime(new Date());
                calendar.add(Calendar.SECOND, offsetSecond);
                Date expiry = calendar.getTime();

                //Cache dell'oggetto
                boolean response = super.set(key, object, expiry);

                //Se l'oggetto è stato cachato
                if (response) {
                    logger.debug("Object stored in cache with key " + key + " EXPIRY " + expiry.toString());
                }

                return response;
            } catch (Exception e) {
                logger.error(
                        "[Exception cacheclient - " +
                                "Impossibile settare cache oggetto " + key + " ]");
                return false;
            }
        } else {
            return false;
        }
    }

    /* (non-Javadoc)
     * @see com.danga.MemCached.MemCachedClient#set(java.lang.String, java.lang.Object)
     */
    public boolean set(String key, Object object) {

        boolean ret = false;
        key = this.prefix + "-" + key;

        if (this.enabled) {
            key = processKey(key);
            try {
                ret = super.set(key, object);
                if (ret) logger.info("[ OK - Object Cached with key " + key + "]");
            } catch (Exception e) {
                logger.error("Error on setting data on cache:" + e.getMessage());
            }
        }
        return ret;
    }

    /* (non-Javadoc)
     * @see com.danga.MemCached.MemCachedClient#delete(java.lang.String)
     */
    public boolean delete(String key) {

        boolean ret = false;
        key = this.prefix + "-" + key;

        if (this.enabled) {
            key = processKey(key);
            ret = super.delete(key);
        }
        return ret;
    }

    private String processKey(String key) {
        key = key.replace(" ", "-");
        return key;
    }

}
